package net.peelmeagrape.hibernate;

import net.peelmeagrape.hibernate.xmlgen.AddClassNameAsAttribute;
import net.peelmeagrape.hibernate.xmlgen.XmlAttribute;
import net.peelmeagrape.hibernate.xmlgen.XmlElement;
import net.peelmeagrape.hibernate.xmlgen.ChildElement;
import net.peelmeagrape.hibernate.sql.SqlDelete;
import net.peelmeagrape.hibernate.sql.SqlUpdate;
import net.peelmeagrape.hibernate.sql.SqlInsert;
import net.peelmeagrape.hibernate.sql.Loader;

import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;

/**
 * Declare a persistent class using {@link H8Class}.
 * Generates a hibernate &lt;class&gt; mapping element
 * (<a href="{@docRoot}/reference/en/html/mapping.html#mapping-declaration-class">Hibernate Documentation for &lt;class&gt;</a>).
 * <br/>
 * It is perfectly acceptable for the named persistent class to be an interface.
 * You would then declare implementing classes of that interface using {@link #subclasses()} or
 * {@link H8Subclass}. You may persist any static inner class.
 * <br/>
 * Each entity should have an id defined. See {@link Id}, {@link CompositeId}.
 */
@Target({TYPE}) @Retention(RUNTIME)
@XmlElement(name="class")
@AddClassNameAsAttribute(name="name")
public @interface H8Class
{
    @ChildElement(0) Meta[] meta() default {};

    /**
     * @deprecated Unsure how this would work as an annotation.
     */
    @XmlAttribute("entity-name") String entityName() default "";

    /**
     * Specifies an interface to use for lazy initializing proxies.
     * You may specify the class itself.
     * This enables lazy initialization of persistent instances of the class.
     * Hibernate will initially return CGLIB proxies which implement the named
     * interface. The actual persistent object will be loaded when a method of
     * the proxy is invoked. See "Proxies for Lazy Initialization" below.
     * todo: make this link.
     */
    @XmlAttribute Class[] proxy() default {};

    /**
     * Lazy fetching may be completely disabled by setting lazy=false.
     */
    @XmlAttribute boolean lazy() default true;

    /**
     * (optional - defaults to the unqualified class name): The name of its database table.
     */
    @XmlAttribute String table() default "";

    /**
     * Override the schema name specified by the root &lt;hibernate-mapping&gt; element.
     */
    @XmlAttribute String schema() default "";

    /**
     * Override the catalog name specified by the root &lt;hibernate-mapping&gt; element.
     */
    @XmlAttribute String catalog() default "";

    /**
     * (optional): Maps an immutable and read-only entity to a database subselect.
     * Useful if you want to have a view instead of a base table, but don't.
     * <br/>
     * There is no difference between a view and a base table for a Hibernate
     * mapping, as expected this is transparent at the database level (note that
     * some DBMS don't support views properly, especially with updates).
     * Sometimes you want to use a view, but can't create one in the database
     * (ie. with a legacy schema). In this case, you can map an immutable and
     * read-only entity to a given SQL subselect expression:<br/>
     * <pre>
     &#64;{@link net.peelmeagrape.hibernate.H8Class}(
        subselect="select item.name, max(bid.amount), count(*) " +
            "from item " +
            "join bid on bid.item_id = item.id " +
            "group by item.name",
        synchronize={&#64;{@link Synchronize}(table="item"), &#64;{@link Synchronize}(table="bid")}
     )</pre>
     * Declare the tables to synchronize this entity with, ensuring that auto-flush happens correctly,
     * and that queries against the derived entity do not return stale data.
     */
    @XmlAttribute String subselect() default "";

    /**
     * (optional - defaults to the class name): A value that distiguishes individual subclasses,
     * used for polymorphic behaviour. Acceptable values include <code>null</code> and
     * <code>not null</code>.
     */
    @XmlAttribute("discriminator-value") String discriminatorValue() default "";

    /**
     * Specifies that instances of the class are (not) mutable.
     * Immutable classes, mutable="false", may not be updated or deleted by the application.
     * This allows Hibernate to make some minor performance optimizations.
     */
    @XmlAttribute boolean mutable() default true;

    /**
     * Used to mark abstract superclasses in <union-subclass> hierarchies.
     * todo: superclass/subclass mapping
     */
    @XmlAttribute("abstract") boolean isabstract() default false;

    /**
     * Determines whether implicit or explicit query polymorphism is used.
     * <br/>
     * Implicit ({@link Polymorphism#IMPLICIT}) polymorphism means that instances of the class will be returned by a
     * query that names any superclass or implemented interface or the class and that
     * instances of any subclass of the class will be returned by a query that names
     * the class itself.
     * <br/>
     * Explicit ({@link Polymorphism#EXPLICIT}) polymorphism means that class instances will be returned only by queries
     * that explicitly name that class and that queries that name the class will return
     * only instances of subclasses mapped inside this &lt;class> declaration as a &lt;subclass>
     * or &lt;joined-subclass>.
     * <br/>
     * For most purposes the default, polymorphism="implicit", is
     * appropriate. Explicit polymorphism is useful when two different classes are mapped
     * to the same table (this allows a "lightweight" class that contains a subset of the
     * table columns).
     */
    @XmlAttribute Polymorphism polymorphism() default Polymorphism.IMPLICIT;

    /**
     * specify an arbitrary SQL WHERE condition to be used when retrieving objects of this class.
     */
    @XmlAttribute String where() default "";

    /**
     * Specifies a custom ClassPersister.
     * Lets you customize the persistence strategy used for the class.
     * You may, for example, specify your own subclass of
     * {@link org.hibernate.persister.entity.BasicEntityPersister}
     * or you might even provide a completely new implementation of the
     * interface {@link org.hibernate.persister.entity.EntityPersister} that implements
     * persistence via, for example, stored procedure calls, serialization to
     * flat files or LDAP. See
     * <code>org.hibernate.test.legacy.CustomPersister</code> for a simple example
     * (of "persistence" to a Hashtable).
     */
    @XmlAttribute Class<? extends org.hibernate.persister.entity.EntityPersister>[] persister() default {};

    /**
     * Specifies that UPDATE SQL should be generated at runtime and contain only those columns
     * whose values have changed.
     * <br/>
     * If you enable dynamic-update, you will have a choice of {@link #optimisticLock()} strategies.
     * <br/>
     * <br/>
     * Note that the dynamic-update and dynamic-insert  settings are not inherited by
     * subclasses and so may also be specified on the &lt;subclass> or &lt;joined-subclass>
     * elements. These settings may increase performance in some cases, but might actually
     * decrease performance in others. Use judiciously.
     */
    @XmlAttribute("dynamic-update") boolean dynamicUpdate() default false;

    /**
     * Specifies that INSERT SQL should be generated at runtime and contain only the columns whose values are not null.
     * Note that the dynamic-update and dynamic-insert  settings are not inherited by
     * subclasses and so may also be specified on the &lt;subclass> or &lt;joined-subclass>
     * elements. These settings may increase performance in some cases, but might actually
     * decrease performance in others. Use judiciously.
     */
    @XmlAttribute("dynamic-insert") boolean dynamicInsert() default false;

    /**
     * specify a "batch size" for fetching instances of this class by identifier.
     */
    @XmlAttribute("batch-size") int batchSize() default 1;

    /**
     * Specifies that Hibernate should never perform an SQL UPDATE unless it is certain
     * that an object is actually modified. In certain cases (actually, only when a transient
     * object has been associated with a new session using update()), this means that Hibernate
     * will perform an extra SQL SELECT to determine if an UPDATE is actually required.
     * <br/>
     *  Use of select-before-update will usually decrease performance. It is very useful to
     * prevent a database update trigger being called unnecessarily if you reattach a graph
     * of detached instances to a Session.
     */
    @XmlAttribute("select-before-update") boolean selectBeforeUpdate() default false;

    /**
     * Determines the optimistic locking strategy.  If you enable dynamic-update, you will have a choice of
     * optimistic locking strategies:
     * <ul>
     * <li>{@link OptimisticLockSetting#VERSION} check the version/timestamp columns</li>
     * <li>{@link OptimisticLockSetting#ALL} check all columns</li>
     * <li>{@link OptimisticLockSetting#DIRTY} check the changed columns, allowing some concurrent updates</li>
     * <li>{@link OptimisticLockSetting#NONE} do not use optimistic locking</li>
     * </ul>
     * We <b>very</b> strongly recommend that you use version/timestamp
     * columns for optimistic locking with Hibernate. This is the optimal strategy with
     * respect to performance and is the only strategy that correctly handles modifications
     * made to detached instances (ie. when {@link org.hibernate.Session#merge(Object)} is used).
     */
    @XmlAttribute("optimistic-lock") OptimisticLockSetting optimisticLock() default OptimisticLockSetting.VERSION;

    /**
     * (optional): A SQL expression used to generate a multi-row check constraint for automatic schema generation.
     */
    @XmlAttribute String check() default "";

    /**
     * (optional): Hibernate can use so called ROWIDs on databases which support. E.g. on Oracle,
     * Hibernate can use the rowid extra column for fast updates if you set this option to rowid.
     * A ROWID is an implementation detail and represents the physical location of a stored tuple.
     */
    @XmlAttribute String rowid() default "";

    Cache[] cache() default {};
    Synchronize[] synchronize() default {};
    Comment[] comment() default {};

    Discriminator[] discriminator() default {};
    NaturalId[] naturalId() default {};

    Join[] join() default {};
    /**
     * One or more {@link Properties} settings.
     */
    Properties[] properties() default {};

    /**
     * Table-per-hierarchy mapping. Must also specify a {@link #discriminator()}.
     */
    Subclass[] subclasses() default {};
    JoinedSubclass[] joinedSubs() default {};
    UnionSubclass[] unionSubs() default {};

    Filter[] filters() default {};

    Loader[] loader() default {};
    SqlInsert[] sqlInsert() default {};
    SqlUpdate[] sqlUpdate() default {};
    SqlDelete[] sqlDelete() default {};

    /**
     * Should queries referencing this class return subclasses or only this class.
     */
    public static enum Polymorphism
    {
        /**
         * Implicit polymorphism means that instances of the class will be returned by a
         * query that names any superclass or implemented interface or the class and that
         * instances of any subclass of the class will be returned by a query that names
         * the class itself.
         */
        IMPLICIT("implicit"),
        /**
         * Explicit polymorphism means that class instances will be returned only by queries
         * that explicitly name that class and that queries that name the class will return
         * only instances of subclasses mapped inside this &lt;class> declaration as a &lt;subclass>
         * or &lt;joined-subclass>.
         */
        EXPLICIT("explicit");
        private String value;

        Polymorphism(String value)
        {
            this.value = value;
        }

        public String toString()
        {
            return value;
        }
    }

    /**
     * What strategy to use for optimistic concurrency.
     */
    public static enum OptimisticLockSetting
    {
        NONE("none"),
        VERSION("version"),
        DIRTY("dirty"),
        ALL("all");

        private String value;

        OptimisticLockSetting(String value)
        {
            this.value = value;
        }

        public String toString()
        {
            return value;
        }
    }
}
